//////////////////////////////////////////////////////////////////////////////
//
//  Created by Philip Mulcahy on 29/05/2012.
//  Copyright (c) 2012 Philip Mulcahy. All rights reserved.
//
//  This file is part of the note-recog library.
//
//  note-recog is free software: you can redistribute it and/or modify
//  it under the terms of version 3 of the GNU Lesser General Public License 
//  as published by the Free Software Foundation.
//
//  note-recog is distributed is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with note-recog. If not, see <http://www.gnu.org/licenses/>.
//
//////////////////////////////////////////////////////////////////////////////

#ifndef note_recog_Optimiser_h
#define note_recog_Optimiser_h

#include <map>
#include <iostream>
#include <set>
#include <string>
#include <vector>
#include "Log.h"
#include <exception>

class Optimiser
{
public:
    class single_input : public std::map<std::string, float>
    {
    public:
        single_input() {}
        const char* comment() const { return _comment.c_str(); }
        void setComment( const char* comment );
    private:
        std::string _comment;
    };
    typedef float (*target_func)(const single_input&);
    class arg_range : public std::set<float>
    {
    public:
        arg_range();
        explicit arg_range( const char * name );
        void setUp( float midPoint, float halfRangeRatio, float intervalRatio );
        const std::string& name() const;
        bool& ignoreLowerBoundPerimeterTouch() { return _allowLowerBoundPerimeterTouch; }
        const bool& ignoreLowerBoundPerimeterTouch() const { return _allowLowerBoundPerimeterTouch; }
    private:
        std::string _name;
        bool _allowLowerBoundPerimeterTouch;
    };
    class input_ranges : public std::map<std::string,arg_range> 
    {
    public:
        void add( const arg_range& );
    };
    typedef std::map<single_input, float> scores;
    class input_set
    {
    public:
        typedef std::set<single_input> inner_type;
        typedef inner_type::value_type value_type;
        typedef inner_type::const_iterator const_iterator;
        typedef inner_type::reverse_iterator reverse_iterator;
    private:
        inner_type _data;
    public:
        const_iterator begin() const { return _data.begin(); }
        const_iterator end() const { return _data.end(); }
        reverse_iterator rbegin() const { return _data.rbegin(); }
        reverse_iterator rend() const { return _data.rend(); }
        input_set() {}
        input_set( const inner_type& in ) : _data(in) {}
        size_t size() const { return _data.size(); }
        single_input centroid() const;
        void insert(value_type value) { _data.insert(value); }
        void clear() { _data.clear(); }
    };
    class InvalidOptimisationException : public std::exception
    {
    public:
        InvalidOptimisationException( const std::string& );
        ~InvalidOptimisationException() throw() {};
        virtual const char * what() const throw ();
    private:
        const std::string _description;
    };
    
public:    
    Optimiser(target_func func,
              const input_ranges& ranges);
    virtual ~Optimiser();
    float getPeak(input_set * inputs) const;
    bool valid() const;
    
private:
    void coverSpace(const single_input& args);    
    void evaluateOnce(const single_input& args);
    bool onPerimeter( const single_input& ) const;
    
private:
    LOG_DECLARE;
    const target_func _target;
    const input_ranges _ranges;
    const std::vector<std::string> _argNames;
    float _optimalOutput;
    float _perimeterOptimumOutput;
    input_set _optimalInputs;
    input_set _perimeterOptimalInputs;
};

std::ostream& operator<<(std::ostream&, const Optimiser::arg_range&);
std::ostream& operator<<(std::ostream&, const Optimiser::input_ranges&);
std::ostream& operator<<(std::ostream&, const Optimiser::single_input&);
std::ostream& operator<<(std::ostream&, const Optimiser::input_set&);

#endif
